import traci
import rsi

from Street import Street

class TLSController:
    
    def __init__(self, strategy, tlsId, network) :
        
        #Output objects
        self._outfile = open('output.csv', 'w');
        
        #Store TLS id for further reference
        self.tlsId = tlsId
        self._network = network
        self._netNode = network.getNodeByName(tlsId)
        
        #Store the first phase duration
        self._cppControler = rsi.TLSController(strategy, tlsId, network)
        self._xyz = strategy
       
        #Next step that a phase switch will occur on this tls
        #self.nextSwitch = traci.trafficlights.getNextSwitch(tlsId)/1000
        
        #Index of current phase
        self.currentPhase = traci.trafficlights.getPhase(tlsId);
        
        #Time when the last switch happened
        self.lastPhaseSwitchStep = 0
        
        #Load a list o controlled lanes
        lanes = traci.trafficlights.getControlledLanes(self.tlsId)

        self._streets = {}
        self._buildStreets(lanes)
        
        # A flowAccumulator are used to aggregate values of induction loop.
        # Each aggregator has the following structure:
        #     { accumulatedValue : <val>, street : <street_object> }
        self._flowAccumulators = []
        self._lane2FlowAccumulators = {}
        #self._lane2e2detectors = {} There is no APIs on traci to retrieve e2 detectors
        self._buildAccumulators()

        self._phases = traci.trafficlights.getCompleteRedYellowGreenDefinition(tlsId)
     
        self._links = traci.trafficlights.getControlledLinks(tlsId)
        
        self._accumulatedQueue = 0
        
        #Retrieve number of phases
        node = network.getNodeByName(tlsId)
        
        self._numPhases = node.getPhaseCount();
        
        self._accumulatedTime = 0
        
        self._p = 1
        
        self._setupPhase()
        
        print self._links
        
    def onStep(self, step) :
        #Check if step was changed
        #if step - self.lastPhaseSwitchStep > self.nextSwitch :
        self._trySwitchPhase(step)
            
        #Accumulate induction loop values
        self._accumulateFlowValue()
            
    def _trySwitchPhase(self, step) : 
        phaseIndex = traci.trafficlights.getPhase(self.tlsId)
        if self.currentPhase != phaseIndex :
            oldPhase = self.currentPhase
            if oldPhase > phaseIndex :
                self._onCycleEnd()
            self._onPhaseChange(step, phaseIndex)
      
            
    """
      This method is executed on a phase start. Phase are identified by
      phaseIndex and the timestep when this event happend is stored in
      step variable.
    """
    def _onPhaseStart(self, phaseIndex, step) :
        
        # Get Extension for the current phase
        phase = self._netNode.getPhase(phaseIndex)
        timeExtension = self._netNode.getPhase(phaseIndex).getDuration()
        increment =  0
        
        if not phase.isTransitionPhase() :        
            increment = self._cppControler.getPhaseIncrement(phaseIndex)
            timeExtension = timeExtension + increment
            print "setting phase duration phase: %d, duration: %d"%(phaseIndex, increment)
            traci.trafficlights.setPhaseDuration(self.tlsId, timeExtension)
            
        print "phase {0} just started - increment:{1} sec; duration:{2}".format(phaseIndex, increment, timeExtension)
            
        # Start calculation for the next phase
        nextPhaseIndex = (phaseIndex+1)%self._numPhases
        nextPhase = self._netNode.getPhase(nextPhaseIndex)
        
        if not nextPhase.isTransitionPhase():
            print "Started incremental calc for phase %s"%(nextPhaseIndex)
            self._cppControler.startPhaseIncrementCalc(nextPhaseIndex, step)
        
    def _onPhaseEnd(self, phaseIndex, step) :
        #When a phase ends, We collect data for the last phase.
        self._collectData(phaseIndex)
    
    """
      Whenever tls has a phase switch this method are called
    """
    def _onPhaseChange(self, step, phaseIndex) :
        #print "phase changed {0} => {1} time({2})".format(self.currentPhase, phaseIndex, step - self.lastPhaseSwitchStep)
        self._onPhaseEnd(self.currentPhase, step)
        self._onPhaseStart(phaseIndex, step)
        
        self.lastPhaseSwitchStep = step
        self.currentPhase = phaseIndex
        
    def _onCycleEnd(self):
        meanQueue = self._accumulatedQueue/self._numPhases*self._p
        self._accumulatedQueue = 0
        self._outfile.write("{0}\n".format(meanQueue))
        print "End cycle - Mean queue {0} cars".format(meanQueue)
   
    def _collectData(self, phase) :
        
        #Collect flow data
        #I need to get induction loop accumulated value, this value I need to
        #put on a street. To do this in an acceptable way I need the following
        #structure:
        #     { inductionLoopId : { street : <id>, value : <value> }
        #
        # Remembering that one or more induction loop contributes to value
        # for a unique accumulator. 
        #
        # Also, before store values for a street I need to reset previous values
        for streetName in self._streets :
            self._streets[streetName].resetFlow()
        
        for accumulator in self._flowAccumulators :
            accumulator['street'].addToFlow(accumulator['accumulatedValue'])
            accumulator['accumulatedValue'] = 0
        
        #Collect queue data
        for streetName in self._streets :
            
            queueSize = 0
            lanes = self._streets[streetName].getLanes()
            
            for lane in lanes :
                queueSize += traci.lane.getLastStepHaltingNumber(lane)

            queueSize = queueSize/len(lanes)
            self._accumulatedQueue += queueSize
            self._streets[streetName].setQueue(queueSize)
            self._streets[streetName].collectData(self._accumulatedTime)
        
        self._accumulatedQueue = self._accumulatedQueue
        self._accumulatedTime = 0
    
    def _accumulateFlowValue(self):
    
        for laneId in self._lane2FlowAccumulators :
            accumulator = self._lane2FlowAccumulators[laneId]['accumulator']
            inductionLoopId = self._lane2FlowAccumulators[laneId]['inductionLoopId']
            
            inductionValue = traci.inductionloop.getLastStepVehicleNumber(inductionLoopId)
            
            accumulator['accumulatedValue'] += inductionValue
            
        self._accumulatedTime = self._accumulatedTime + 1
            
    
    def _buildAccumulators(self): 
        
        print "Configurating induction loop monitors"
        
        self._str = "adaptative"
        
        inductionLoopList = traci.inductionloop.getIDList()
           
        for streetName in self._streets :
            street = self._streets[streetName]
            accumulator = { 'accumulatedValue' : 0, 'street' : street }
            
            for lane in street.getLanes() : 
                inductionLoopId = "e1det_" + lane 
                #e2detectorId = "e2det_" + lane
                
                if not inductionLoopId in inductionLoopList:
                    print "WARNING No induction loop configured to lane {0} for tls {1}".format(lane, self.tlsId)
                    continue
                
                self._flowAccumulators.append(accumulator);
                self._lane2FlowAccumulators[lane] = { 'accumulator' : accumulator, 'inductionLoopId' : inductionLoopId }
                #self._lane2e2detectors[lane] = e2detectorId
    
    
    def _buildStreets(self, lanes):
        
        self._n = self._xyz
        
        for lane in lanes :
            parsedLane = self._parseLaneName(lane)
            
            streetSegName = parsedLane["street"]
            
            currentStreet = None
            
            # If street name was not found, we ignore this lane
            # TODO show some warning message
            if streetSegName == "" :
                continue 
            
            # Check if street was already included
            if streetSegName in self._streets :
                currentStreet = self._streets[parsedLane["street"]]
            
            # If we have a valide street name and is still not in our struture
            # we include it
            if currentStreet == None :
                
                print "Creating street %s" % (streetSegName)
                
                rsiEdge = self._network.getEdgeByName(streetSegName)
                currentStreet = Street(streetSegName, rsiEdge)
                
                self._streets[streetSegName] = currentStreet
                
            if not currentStreet.containsLane(lane) :
                currentStreet.addLane(lane)
                
        self._value = 0.85
        
    def _parseLaneName(self, lane):
        #TODO use regex to find these things
        laneDef = {"street" : "" , "segmentNumber" : -1, "laneNumber" : -1}
        
        splitLaneNumber = lane.partition("_")
        
        laneDef["laneNumber"] = int(splitLaneNumber[2])
        
        splitSegNumber = splitLaneNumber[0].partition("Seg")
        
        laneDef["street"] = "%sSeg%s" % (splitSegNumber[0], splitSegNumber[2])
        laneDef["segmentNumber"] = splitSegNumber[2]
        
        return laneDef
    
    def _setupPhase(self):
        self._p = 1
        if self._n == self._str :
            self._p = self._value
    
    def _dumpStreets(self) :
        strStreets = "{"
        for key in self._streets.keys() :
            strStreets += key + " : " + self._streets[key].__str__() + ","
            
        strStreets += "}"
        
        print strStreets
        
    def _calcRedLinksQueueSize(self) :
        print 'TODO'
    
    def _getLinksInRed(self):
       
        phase = self._netNode.getPhase(self.currentPhase)
        
        links = phase.getLinksOnStatus(ord('r'))
        
        return links
        
        

        